Progressive Web App Architecture: JavaScript Service Worker Patterns | MLOG | MLOG

4. Network-Only

The network-only strategy always fetches assets from the network, bypassing the cache entirely. This strategy is used when you absolutely need the latest version of a resource and caching is not desired.

Example:

            
self.addEventListener('fetch', event => {
  event.respondWith(
    fetch(event.request)
  );
});

            

5. Stale-While-Revalidate

The stale-while-revalidate strategy serves the cached asset immediately while simultaneously fetching the latest version from the network. Once the network request completes, the cache is updated with the new version. This strategy provides a fast initial response while ensuring that the user eventually receives the most up-to-date content. This is a useful strategy for non-critical content that benefits from speed over absolute freshness.

Example:

            
self.addEventListener('fetch', event => {
  event.respondWith(
    caches.match(event.request)
      .then(response => {
        const fetchPromise = fetch(event.request).then(networkResponse => {
          caches.open('my-cache').then(cache => {
            cache.put(event.request, networkResponse.clone());
            return networkResponse;
          });
        });
        return response || fetchPromise;
      })
  );
});

            

6. Cache, then Network

Similar to stale-while-revalidate but without the immediate return of the cached asset. It checks the cache first, and only if the asset is present will the network request proceed in the background to update the cache.

Choosing the Right Caching Strategy

The optimal caching strategy depends on the specific requirements of your application. Consider factors such as:

By carefully selecting the appropriate caching strategies, you can significantly improve the performance and user experience of your PWA, even in offline environments. Tools like Workbox ([https://developers.google.com/web/tools/workbox](https://developers.google.com/web/tools/workbox)) can simplify the implementation of these strategies.

Background Synchronization: Handling Offline Mutations

Background synchronization allows your PWA to perform tasks in the background, even when the user is offline. This is particularly useful for handling form submissions, data updates, and other operations that require network connectivity. The `BackgroundSyncManager` API enables you to register tasks that will be executed when the network becomes available.

Registering a Background Sync Task

To register a background sync task, you need to use the `register` method of the `BackgroundSyncManager`. This method takes a unique tag name as an argument. The tag name identifies the specific task to be performed.

Example:

            
self.addEventListener('sync', event => {
  if (event.tag === 'my-sync-task') {
    event.waitUntil(doSomeWork());
  }
});

            

Handling the Sync Event

When the browser detects network connectivity, it dispatches a `sync` event to the service worker. You can listen for this event and perform the necessary actions, such as sending data to the server.

Example:

            
async function doSomeWork() {
  // Retrieve data from IndexedDB
  const data = await getDataFromIndexedDB();

  // Send data to the server
  try {
    const response = await fetch('/api/sync', {
      method: 'POST',
      body: JSON.stringify(data),
      headers: {
        'Content-Type': 'application/json'
      }
    });

    if (response.ok) {
      // Clear the data from IndexedDB
      await clearDataFromIndexedDB();
    } else {
      // Handle errors
      console.error('Sync failed:', response.status);
      throw new Error('Sync failed');
    }
  } catch (error) {
    // Handle network errors
    console.error('Network error:', error);
    throw error;
  }
}

            

Example: Offline Form Submission

Imagine a scenario where a user fills out a form while offline. The service worker can store the form data in IndexedDB and register a background sync task. When the network becomes available, the service worker will retrieve the form data from IndexedDB and submit it to the server.

  1. User fills out the form and clicks submit while offline.
  2. The form data is stored in IndexedDB.
  3. A background sync task is registered with a unique tag (e.g., `form-submission`).
  4. When the network is available, the `sync` event is triggered.
  5. The service worker retrieves the form data from IndexedDB and submits it to the server.
  6. If the submission is successful, the form data is removed from IndexedDB.

Push Notifications: Engaging Users with Timely Updates

Push notifications enable your PWA to send timely updates and messages to users, even when the app is not actively running in the browser. This can significantly improve user engagement and retention. The Push API and Notifications API work together to deliver push notifications.

Subscribing to Push Notifications

To receive push notifications, users must first grant permission to your PWA. You can use the `PushManager` API to subscribe users to push notifications.

Example:

            
navigator.serviceWorker.ready.then(registration => {
  registration.pushManager.subscribe({
    userVisibleOnly: true,
    applicationServerKey: 'YOUR_PUBLIC_VAPID_KEY'
  })
  .then(subscription => {
    // Send subscription details to your server
    sendSubscriptionToServer(subscription);
  })
  .catch(error => {
    console.error('Failed to subscribe:', error);
  });
});

            

Important: Replace `YOUR_PUBLIC_VAPID_KEY` with your actual VAPID (Voluntary Application Server Identification) key. VAPID keys are used to identify your application server and ensure that push notifications are sent securely.

Handling Push Notifications

When a push notification is received, the service worker dispatches a `push` event. You can listen for this event and display the notification to the user.

Example:

            
self.addEventListener('push', event => {
  const payload = event.data ? event.data.text() : 'No payload';

  event.waitUntil(
    self.registration.showNotification('My PWA', {
      body: payload,
      icon: 'icon.png'
    })
  );
});

            

Customizing Push Notifications

The Notifications API allows you to customize the appearance and behavior of push notifications. You can specify the title, body, icon, badge, and other options.

Example:

            
self.addEventListener('push', event => {
  const data = event.data.json();
  const title = data.title || 'My PWA';
  const options = {
    body: data.body || 'No message',
    icon: data.icon || 'icon.png',
    badge: data.badge || 'badge.png',
    vibrate: [200, 100, 200],
    data: { // Custom data that you can access when the user clicks the notification
      url: data.url || '/'
    },
    actions: [
      {action: 'explore', title: 'Explore this new world',
        icon: 'images/checkmark.png'},
      {action: 'close', title: 'Close',
        icon: 'images/xmark.png'},
    ]
  };

  event.waitUntil(self.registration.showNotification(title, options));
});


self.addEventListener('notificationclick', function(event) {
  event.notification.close();

  // Check if the user clicked on an action.
  if (event.action === 'explore') {
    clients.openWindow(event.notification.data.url);
  } else {
    // Default action: open the app.
    clients.openWindow('/');
  }
});

            

Example: News Alert

A news application can use push notifications to alert users about breaking news stories. When a new article is published, the server sends a push notification to the user's device, displaying a brief summary of the article. The user can then click on the notification to open the full article in the PWA.

Advanced Service Worker Patterns

1. Offline Analytics

Track user behavior even when they are offline by storing analytics data locally and sending it to the server when the network is available. This can be achieved using IndexedDB and Background Sync.

2. Versioning and Updating

Implement a robust versioning strategy for your service worker to ensure that users always receive the latest updates without disrupting their experience. Use cache busting techniques to invalidate old cached assets.

3. Modular Service Workers

Organize your service worker code into modules to improve maintainability and readability. Use JavaScript modules (ESM) or a module bundler like Webpack or Rollup.

4. Dynamic Caching

Cache assets dynamically based on user interactions and usage patterns. This can help to optimize the cache size and improve performance.

Best Practices for Service Worker Development

Conclusion

JavaScript service workers are powerful tools for building robust, performant, and engaging PWAs. By understanding the service worker lifecycle and implementing appropriate caching strategies, background synchronization, and push notifications, you can create exceptional user experiences, even in offline environments. This article has explored key service worker patterns and best practices to guide you in building successful PWAs for a global audience. As the web continues to evolve, service workers will play an increasingly important role in shaping the future of web development.

Remember to adapt these patterns to your specific application requirements and always prioritize the user experience. By embracing the power of service workers, you can create PWAs that are not only functional but also delightful to use, regardless of the user's location or network connection.

Further Resources: